/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
 *
 * The contents of this file are subject to the terms of either the GNU
 * General Public License Version 2 only ("GPL") or the Common Development
 * and Distribution License("CDDL") (collectively, the "License").  You
 * may not use this file except in compliance with the License. You can obtain
 * a copy of the License at https://glassfish.dev.java.net/public/CDDL+GPL.html
 * or glassfish/bootstrap/legal/LICENSE.txt.  See the License for the specific
 * language governing permissions and limitations under the License.
 *
 * When distributing the software, include this License Header Notice in each
 * file and include the License file at glassfish/bootstrap/legal/LICENSE.txt.
 * Sun designates this particular file as subject to the "Classpath" exception
 * as provided by Sun in the GPL Version 2 section of the License file that
 * accompanied this code.  If applicable, add the following below the License
 * Header, with the fields enclosed by brackets [] replaced by your own
 * identifying information: "Portions Copyrighted [year]
 * [name of copyright owner]"
 *
 * Contributor(s):
 *
 * If you wish your version of this file to be governed by only the CDDL or
 * only the GPL Version 2, indicate your decision by adding "[Contributor]
 * elects to include this software in this distribution under the [CDDL or GPL
 * Version 2] license."  If you don't indicate a single choice of license, a
 * recipient has the option to distribute your version of this file under
 * either the CDDL, the GPL Version 2 or to extend the choice of license to
 * its licensees as provided above.  However, if you add GPL Version 2 code
 * and therefore, elected the GPL Version 2 license, then the option applies
 * only if the new code is made subject to such option by the copyright
 * holder.
 */

/*
 * @(#)MimetypesFileTypeMap.java	1.18 07/05/14
 */

package javax.activation;

import java.io.*;
import java.net.*;
import java.util.*;
import com.sun.activation.registries.MimeTypeFile;
import com.sun.activation.registries.LogSupport;

/**
 * This class extends FileTypeMap and provides data typing of files
 * via their file extension. It uses the <code>.mime.types</code> format.
 * <p>
 * 
 * <b>MIME types file search order:</b>
 * <p>
 * The MimetypesFileTypeMap looks in various places in the user's system for
 * MIME types file entries. When requests are made to search for MIME types in
 * the MimetypesFileTypeMap, it searches MIME types files in the following
 * order:
 * <p>
 * <ol>
 * <li>Programmatically added entries to the MimetypesFileTypeMap instance.
 * <li>The file <code>.mime.types</code> in the user's home directory.
 * <li>The file &lt;<i>java.home</i>&gt;<code>/lib/mime.types</code>.
 * <li>The file or resources named <code>META-INF/mime.types</code>.
 * <li>The file or resource named <code>META-INF/mimetypes.default</code>
 * (usually found only in the <code>activation.jar</code> file).
 * </ol>
 * <p>
 * <b>MIME types file format:</b>
 * <p>
 * 
 * <code>
 * # comments begin with a '#'<br>
 * # the format is &lt;mime type> &lt;space separated file extensions><br>
 * # for example:<br>
 * text/plain    txt text TXT<br>
 * # this would map file.txt, file.text, and file.TXT to<br>
 * # the mime type "text/plain"<br>
 * </code>
 * 
 * @author Bart Calder
 * @author Bill Shannon
 */
public class MimetypesFileTypeMap extends FileTypeMap {
	/*
	 * We manage a collection of databases, searched in order.
	 * The default database is shared between all instances
	 * of this class.
	 * XXX - Can we safely share more databases between instances?
	 */
	private static MimeTypeFile defDB = null;
	private MimeTypeFile[] DB;
	private static final int PROG = 0; // programmatically added entries

	private static String defaultType = "application/octet-stream";

	/**
	 * The default constructor.
	 */
	public MimetypesFileTypeMap() {
		Vector dbv = new Vector(5); // usually 5 or less databases
		MimeTypeFile mf = null;
		dbv.addElement(null); // place holder for PROG entry

		LogSupport.log("MimetypesFileTypeMap: load HOME");
		try {
			String user_home = System.getProperty("user.home");

			if (user_home != null) {
				String path = user_home + File.separator + ".mime.types";
				mf = loadFile(path);
				if (mf != null)
					dbv.addElement(mf);
			}
		} catch (SecurityException ex) {
		}

		LogSupport.log("MimetypesFileTypeMap: load SYS");
		try {
			// check system's home
			String system_mimetypes = System.getProperty("java.home") + File.separator + "lib" + File.separator + "mime.types";
			mf = loadFile(system_mimetypes);
			if (mf != null)
				dbv.addElement(mf);
		} catch (SecurityException ex) {
		}

		LogSupport.log("MimetypesFileTypeMap: load JAR");
		// load from the app's jar file
		loadAllResources(dbv, "mime.types");

		LogSupport.log("MimetypesFileTypeMap: load DEF");
		synchronized (MimetypesFileTypeMap.class) {
			// see if another instance has created this yet.
			if (defDB == null)
				defDB = loadResource("/mimetypes.default");
		}

		if (defDB != null)
			dbv.addElement(defDB);

		DB = new MimeTypeFile[dbv.size()];
		dbv.copyInto(DB);
	}

	/**
	 * Load from the named resource.
	 */
	private MimeTypeFile loadResource(String name) {
		InputStream clis = null;
		try {
			clis = SecuritySupport.getResourceAsStream(this.getClass(), name);
			if (clis != null) {
				MimeTypeFile mf = new MimeTypeFile(clis);
				if (LogSupport.isLoggable())
					LogSupport.log("MimetypesFileTypeMap: successfully " + "loaded mime types file: " + name);
				return mf;
			} else {
				if (LogSupport.isLoggable())
					LogSupport.log("MimetypesFileTypeMap: not loading " + "mime types file: " + name);
			}
		} catch (IOException e) {
			if (LogSupport.isLoggable())
				LogSupport.log("MimetypesFileTypeMap: can't load " + name, e);
		} catch (SecurityException sex) {
			if (LogSupport.isLoggable())
				LogSupport.log("MimetypesFileTypeMap: can't load " + name, sex);
		} finally {
			try {
				if (clis != null)
					clis.close();
			} catch (IOException ex) {
			} // ignore it
		}
		return null;
	}

	/**
	 * Load all of the named resource.
	 */
	private void loadAllResources(Vector v, String name) {
		boolean anyLoaded = false;
		try {
			URL[] urls;
			ClassLoader cld = null;
			// First try the "application's" class loader.
			cld = SecuritySupport.getContextClassLoader();
			if (cld == null)
				cld = this.getClass().getClassLoader();
			if (cld != null)
				urls = SecuritySupport.getResources(cld, name);
			else
				urls = SecuritySupport.getSystemResources(name);
			if (urls != null) {
				if (LogSupport.isLoggable())
					LogSupport.log("MimetypesFileTypeMap: getResources");
				for (int i = 0; i < urls.length; i++) {
					URL url = urls[i];
					InputStream clis = null;
					if (LogSupport.isLoggable())
						LogSupport.log("MimetypesFileTypeMap: URL " + url);
					try {
						clis = SecuritySupport.openStream(url);
						if (clis != null) {
							v.addElement(new MimeTypeFile(clis));
							anyLoaded = true;
							if (LogSupport.isLoggable())
								LogSupport.log("MimetypesFileTypeMap: " + "successfully loaded " + "mime types from URL: " + url);
						} else {
							if (LogSupport.isLoggable())
								LogSupport.log("MimetypesFileTypeMap: " + "not loading " + "mime types from URL: " + url);
						}
					} catch (IOException ioex) {
						if (LogSupport.isLoggable())
							LogSupport.log("MimetypesFileTypeMap: can't load " + url, ioex);
					} catch (SecurityException sex) {
						if (LogSupport.isLoggable())
							LogSupport.log("MimetypesFileTypeMap: can't load " + url, sex);
					} finally {
						try {
							if (clis != null)
								clis.close();
						} catch (IOException cex) {
						}
					}
				}
			}
		} catch (Exception ex) {
			if (LogSupport.isLoggable())
				LogSupport.log("MimetypesFileTypeMap: can't load " + name, ex);
		}

		// if failed to load anything, fall back to old technique, just in case
		if (!anyLoaded) {
			LogSupport.log("MimetypesFileTypeMap: !anyLoaded");
			MimeTypeFile mf = loadResource("/" + name);
			if (mf != null)
				v.addElement(mf);
		}
	}

	/**
	 * Load the named file.
	 */
	private MimeTypeFile loadFile(String name) {
		MimeTypeFile mtf = null;

		try {
			mtf = new MimeTypeFile(name);
		} catch (IOException e) {
			// e.printStackTrace();
		}
		return mtf;
	}

	/**
	 * Construct a MimetypesFileTypeMap with programmatic entries
	 * added from the named file.
	 * 
	 * @param mimeTypeFileName
	 *            the file name
	 */
	public MimetypesFileTypeMap(String mimeTypeFileName) throws IOException {
		this();
		DB[PROG] = new MimeTypeFile(mimeTypeFileName);
	}

	/**
	 * Construct a MimetypesFileTypeMap with programmatic entries
	 * added from the InputStream.
	 * 
	 * @param is
	 *            the input stream to read from
	 */
	public MimetypesFileTypeMap(InputStream is) {
		this();
		try {
			DB[PROG] = new MimeTypeFile(is);
		} catch (IOException ex) {
			// XXX - really should throw it
		}
	}

	/**
	 * Prepend the MIME type values to the registry.
	 * 
	 * @param mime_types
	 *            A .mime.types formatted string of entries.
	 */
	public synchronized void addMimeTypes(String mime_types) {
		// check to see if we have created the registry
		if (DB[PROG] == null)
			DB[PROG] = new MimeTypeFile(); // make one

		DB[PROG].appendToRegistry(mime_types);
	}

	/**
	 * Return the MIME type of the file object.
	 * The implementation in this class calls
	 * <code>getContentType(f.getName())</code>.
	 * 
	 * @param f
	 *            the file
	 * @return the file's MIME type
	 */
	public String getContentType(File f) {
		return this.getContentType(f.getName());
	}

	/**
	 * Return the MIME type based on the specified file name.
	 * The MIME type entries are searched as described above under
	 * <i>MIME types file search order</i>.
	 * If no entry is found, the type "application/octet-stream" is returned.
	 * 
	 * @param filename
	 *            the file name
	 * @return the file's MIME type
	 */
	public synchronized String getContentType(String filename) {
		int dot_pos = filename.lastIndexOf("."); // period index

		if (dot_pos < 0)
			return defaultType;

		String file_ext = filename.substring(dot_pos + 1);
		if (file_ext.length() == 0)
			return defaultType;

		for (int i = 0; i < DB.length; i++) {
			if (DB[i] == null)
				continue;
			String result = DB[i].getMIMETypeString(file_ext);
			if (result != null)
				return result;
		}
		return defaultType;
	}

	/**
	 * for debugging...
	 * 
	 * public static void main(String[] argv) throws Exception {
	 * MimetypesFileTypeMap map = new MimetypesFileTypeMap();
	 * System.out.println("File " + argv[0] + " has MIME type " +
	 * map.getContentType(argv[0]));
	 * System.exit(0);
	 * }
	 */
}
